﻿using System.Windows;
using DeepEarth.Map.Core;
using System.Windows.Data;
using System.Collections.Generic;
using System;
using DeepEarth.Map.Core.Utilities;
using System.Windows.Controls;
using System.Windows.Media.Effects;
using System.Windows.Media;
using DeepEarth.Core.Utilities;
using DeepEarth.Core;
using System.Windows.Input;
using DeepEarth.Map.Core.Extension;

namespace DeepEarth.Toolkit.Geometry
{
    public class PointBase : ContentControl, IScalable, ILocatable 
    {
        public static readonly DependencyProperty PositionProperty =
            DependencyProperty.Register("Position", typeof(Location), typeof(PointBase), new PropertyMetadata(PositionChanged));

        public Location Position
        {
            get { return (Location)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }

        static void PositionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
        }

        public static readonly DependencyProperty PositionOriginProperty =
            DependencyProperty.Register("PositionOrigin", typeof(PositionOrigin), typeof(PointBase), new PropertyMetadata(PositionOriginPropertyChanged));

        public PositionOrigin PositionOrigin
        {
            get { return (PositionOrigin)GetValue(PositionOriginProperty); }
            set { SetValue(PositionOriginProperty, value); }
        }

        static void PositionOriginPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null)
            {
                PositionOrigin po = (PositionOrigin)args.NewValue;

                PointBase pb = sender as PointBase;
                pb.RenderTransformOrigin = new Point
                {
                    X = po.X,
                    Y = po.Y
                };
            }
        }
        
        bool enableDrag = false;
        public bool EnableDrag
        {
            get
            {
                return enableDrag;
            }
            set
            {
                enableDrag = value;

                if (value && !dragHandlerInitialized)
                {
                    SetupMouseEventHandlers(null, null);
                }
            }
        }

        public PointBase()
        {
            Binding locationBinding = new Binding("Position") { Source = this, Mode = BindingMode.OneWay };
            this.SetBinding(Layer.PositionProperty, locationBinding);

            Binding originBinding = new Binding("PositionOrigin") { Source = this, Mode = BindingMode.OneWay };
            this.SetBinding(Layer.PositionOriginProperty, originBinding);

            this.InitializationConditions.Add(() =>
            {
                FrameworkElement root = Application.Current.RootVisual as FrameworkElement;
                return (root != null && root.ActualWidth > 0);
            });

            this.LayoutUpdated += AttemptInitialization;

            this.Loaded += SetupMouseEventHandlers;
        }


        protected List<Action> InitializationActions = new List<Action>();

        protected List<Func<bool>> InitializationConditions = new List<Func<bool>>();

        void AttemptInitialization(object sender, EventArgs e)
        {
            bool condition = InitializationConditions.Count == 0;
            foreach (var c in InitializationConditions)
            {
                condition = c();
                if (!condition)
                {
                    break;
                }
            }

            if (condition || InitializationActions.Count == 0)
            {
                this.LayoutUpdated -= AttemptInitialization;
                InitializationActions.Iter(a => a());
                InitializationActions.Clear();
            }
        }

        public static readonly DependencyProperty ScaleProperty =
            DependencyProperty.Register("Scale", typeof(double), typeof(PointBase), new PropertyMetadata(1.0, ScaleChangedHandler));

        public virtual double Scale
        {
            get
            {
                return (double)GetValue(ScaleProperty);
            }
            set
            {
                SetValue(ScaleProperty, value);
            }
        }

        static void ScaleChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null)
            {
                PointBase pb = sender as PointBase;

                double newValue = (double)args.NewValue;

                pb.ApplyScale(newValue, 1.0);
            }
        }

        public virtual void ApplyScale(double scale, double appliedScaleTransform)
        {
        }

        private bool constantSize = false;
        public virtual bool MaintainConstantSize
        {
            get
            {
                return constantSize;
            }
            set
            {
                constantSize = value;
            }
        }

        public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.Register("IsSelected", typeof(bool), typeof(PointBase), new PropertyMetadata(IsSelectedChanged));

        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        static void IsSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            PointBase pb = sender as PointBase;

            bool newValue = (bool)args.NewValue;

#if !WINDOWS_PHONE
            if (newValue)
            {
                pb.Effect = pb.SelectedEffect;
            }
            else
            {
                pb.Effect = null;
            }
#endif

            // 1. Child classes can just override this method for efficiency
            pb.SelectedChangedOverride();

            if (pb.IsSelectedChangedHandler != null)
            {
                pb.IsSelectedChangedHandler(newValue);
            }
        }

        protected virtual void SelectedChangedOverride()
        {
        }

        public static readonly DependencyProperty IsSelectedChangedHandlerProperty = DependencyProperty.Register(
            "IsSelectedChangedHandler",
            typeof(Action<bool>),
            typeof(PointBase),
            null);

        public Action<bool> IsSelectedChangedHandler
        {
            get { return (Action<bool>)GetValue(IsSelectedChangedHandlerProperty); }
            set
            {
                SetValue(IsSelectedChangedHandlerProperty, value);
            }
        }

#if !WINDOWS_PHONE
        public static readonly DependencyProperty SelectedEffectProperty =
    DependencyProperty.Register("SelectedEffect", typeof(Effect), typeof(PointBase),
                    null);

                // Drop shadow effect is too resource intensive
                    //new PropertyMetadata(new DropShadowEffect
                    //{
                    //    BlurRadius = 100,
                    //    Color = Colors.Purple,
                    //    ShadowDepth = 0
                    //}));

        public Effect SelectedEffect
        {
            get { return (Effect)GetValue(SelectedEffectProperty); }
            set { SetValue(SelectedEffectProperty, value); }
        }
#endif

        UIElementLayer parentLayer;
        protected UIElementLayer ParentLayer
        {
            get
            {
                if (parentLayer == null)
                {
                    parentLayer = DeepEarth.Map.Core.Utilities.VisualTree.FindAncestor<UIElementLayer>(this);
                }

                return parentLayer;
            }
        }

        IMap map;
        protected IMap Map
        {
            get
            {
                if (map == null)
                {
                    map = DeepEarth.Map.Core.Utilities.VisualTree.FindAncestor<IMap>(this);
                }

                return map;
            }
        }

        // TODO - Can Rx be used to deprecate these variables?
        bool isDragging = false;
        Point lastDragPoint;
        Location lastDragLocation;
        bool hasDragged = false;

        bool dragHandlerInitialized = false;

        protected virtual void Translate(Point delta, Location locationDelta)
        {
            Location l = this.Position.Clone();
            l.Latitude += locationDelta.Latitude;
            l.Longitude += locationDelta.Longitude;

            this.Position = l;
        }

        void SetupMouseEventHandlers(object sender, EventArgs dontCare)
        {
            this.Loaded -= SetupMouseEventHandlers;

            if (EnableDrag)
            {
                this.MouseLeftButtonDown += (o, e) =>
                {
                    if (!EnableDrag)
                        return;

                    isDragging = true;
                    lastDragPoint = e.GetPosition(Map.AsFrameworkElement);
                    lastDragLocation = this.Map.ViewportPointToLocation(lastDragPoint);

                    EnableInteractionSurface();
                    e.Handled = true;
                    hasDragged = false;
                };

                dragHandlerInitialized = true;
            }            
        }

        void EnableInteractionSurface()
        {
            this.Map.InteractionSurface.IsHitTestVisible = true;
            this.Map.InteractionSurface.CaptureMouse();

            this.Map.IsInteractive = false;

            this.Map.InteractionSurface.MouseMove += new System.Windows.Input.MouseEventHandler(InteractionSurface_MouseMove);
            this.Map.InteractionSurface.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(InteractionSurface_MouseLeftButtonUp);
        }

        void InteractionSurface_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            this.Map.InteractionSurface.MouseMove -= InteractionSurface_MouseMove;
            this.Map.InteractionSurface.MouseLeftButtonUp -= InteractionSurface_MouseLeftButtonUp;

            this.Map.InteractionSurface.ReleaseMouseCapture();
            this.Map.InteractionSurface.IsHitTestVisible = false;

            this.map.IsInteractive = true;

            isDragging = false;

            if (!hasDragged)
            {
                this.IsSelected = !IsSelected;
            }
            else
            {
                this.IsSelected = false;
            }
        }

        void InteractionSurface_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (isDragging)
            {                
                hasDragged = true;
                this.IsSelected = true;

                Point currentPosition = e.GetPosition(Map.AsFrameworkElement);
                Location currentLocation = this.Map.ViewportPointToLocation(currentPosition);
                Point delta = new Point(currentPosition.X - lastDragPoint.X, currentPosition.Y - lastDragPoint.Y);
                Location locationDelta = new Location
                {
                    Longitude = currentLocation.Longitude - lastDragLocation.Longitude,
                    Latitude = currentLocation.Latitude - lastDragLocation.Latitude
                };
                lastDragPoint = currentPosition;
                lastDragLocation = currentLocation;
                Translate(delta, locationDelta);
            }
        }
    }
}
